package zhoug.common.encrypt;

import android.util.Base64;

import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import androidx.annotation.NonNull;
import zhoug.common.utils.Base64Utils;
import zhoug.common.utils.IOUtils;


/**
 * AES对称加密
 * 使用{@link Builder}创建
 * <p>
 * 算法/模式/填充                16字节加密后数据长度        不满16字节加密后长度
 * AES/CBC/NoPadding             16                          不支持
 * AES/CBC/PKCS5Padding          32                          16
 * AES/CBC/ISO10126Padding       32                          16
 * AES/CFB/NoPadding             16                          原始数据长度
 * AES/CFB/PKCS5Padding          32                          16
 * AES/CFB/ISO10126Padding       32                          16
 * AES/ECB/NoPadding             16                          不支持
 * AES/ECB/PKCS5Padding          32                          16
 * AES/ECB/ISO10126Padding       32                          16
 * AES/OFB/NoPadding             16                          原始数据长度
 * AES/OFB/PKCS5Padding          32                          16
 * AES/OFB/ISO10126Padding       32                          16
 * AES/PCBC/NoPadding            16                          不支持
 * AES/PCBC/PKCS5Padding         32                          16
 * AES/PCBC/ISO10126Padding      32                          16
 *
 * @Author 35574
 * @Date 2021/7/28
 * @Description
 */
public class AESHelper implements IEncryptor {
    // 密匙算法:AES加密
    private static final String AES = "AES";

    /**
     * 字符串编码方式
     */
    private Charset charset;

    /**
     * 编码和解码的flag
     * {@link Base64#DEFAULT} :最后有个换行符\n
     * {@link Base64#NO_PADDING} :这个参数是略去加密字符串最后的”=”
     * {@link Base64#NO_WRAP} :这个参数意思是略去所有的换行符
     */
    private int base64Flag;
    /**
     * 算法/模式/填充
     */
    private String transformation;

    /**
     * 密匙
     * 支持128,192,256位 即16,24,32个字节
     */
    private String key;
    /**
     * 密码
     */
    private SecretKeySpec mKey;
    /**
     * 向量:16字节
     */
    private String iv;
    /**
     * 向量,除了ECB加密模式之外的必须设置IV
     */
    private IvParameterSpec mIv;
    private Base64Utils _mBase64Utils;

    public static final class Builder {
        private Charset charset = StandardCharsets.UTF_8;
        private int base64Flag = Base64.NO_WRAP;
        private String transformation = EncryptConstants.TRANSFORMATION_AES_CBC_P7;
        private String key;
        private String iv;

        public Builder() {
        }

        /**
         * 字符串编码方式 默认:StandardCharsets.UTF_8;
         *
         * @param charset
         * @return
         */
        public Builder setCharset(@NonNull Charset charset) {
            this.charset = charset;
            return this;
        }

        /**
         * 设置base64 flag 默认:Base64.NO_WRAP
         *
         * @param base64Flag
         * @return
         */
        public Builder setBase64Flag(int base64Flag) {
            this.base64Flag = base64Flag;
            return this;
        }

        /**
         * 算法/模式/填充 默认 "AES/ECB/PKCS7Padding"
         *
         * @param transformation
         * @return
         */
        public Builder setTransformation(@NonNull String transformation) {
            this.transformation = transformation;
            return this;
        }

        /**
         * 设置密码
         *
         * @param key
         * @return
         */
        public Builder setKey(String key) {
            this.key = key;
            return this;
        }

        public Builder setIv(String iv) {
            this.iv = iv;
            return this;
        }

        public AESHelper build() {
            AESHelper aesHelper = new AESHelper();
            //base64配置
            aesHelper.charset = charset;
            aesHelper.base64Flag = base64Flag;
            aesHelper.transformation = transformation;
            aesHelper.key = key;
            aesHelper.iv = iv;
            return aesHelper;
        }

    }

    private AESHelper() {
    }

    /**
     * 获取密码
     *
     * @return
     */
    private SecretKeySpec getKey() {
        if (mKey == null) {
            synchronized (this) {
                if (mKey == null) {
                    mKey = new SecretKeySpec(key.getBytes(), AES);
                }
            }
        }
        return mKey;
    }

    /**
     * 加盐
     *
     * @return
     */
    private IvParameterSpec getIv() {
        if (iv != null && mIv == null) {
            synchronized (this) {
                if (mIv == null) {
                    mIv = new IvParameterSpec(iv.getBytes());
                }
            }
        }
        return mIv;
    }

    /**
     * 创建加密器
     *
     * @return
     */
    private Cipher createEncryptCipher() {
        Cipher cp = null;
        try {
            cp = Cipher.getInstance(transformation);
            IvParameterSpec iv = getIv();
            if (iv != null) {
                cp.init(Cipher.ENCRYPT_MODE, getKey(), iv);
            } else {
                cp.init(Cipher.ENCRYPT_MODE, getKey());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cp;
    }

    /**
     * 创建解密器
     *
     * @return
     */
    private Cipher createDecryptCipher() {
        Cipher cp = null;
        try {
            cp = Cipher.getInstance(transformation);
            IvParameterSpec iv = getIv();
            if (iv != null) {
                cp.init(Cipher.DECRYPT_MODE, getKey(), iv);
            } else {
                cp.init(Cipher.DECRYPT_MODE, getKey());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cp;
    }

    /**
     * 获取 Base64Utils
     *
     * @return
     */
    public Base64Utils getBase64Utils() {
        if (_mBase64Utils == null) {
            synchronized (this) {
                if (_mBase64Utils == null) {
                    _mBase64Utils = new Base64Utils(charset, base64Flag);
                }
            }
        }
        return _mBase64Utils;
    }

    /**
     * 加密
     *
     * @param data 源数据
     * @return 加密后的字符数组
     */
    public byte[] encrypt(byte[] data) {
        if (data == null) {
            return null;
        }
        try {
            return createEncryptCipher().doFinal(data);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return data;
    }

    @Override
    public byte[] encrypt(byte[] data, int inputOffset, int inputLength) {
        if (data == null) {
            return null;
        }
        try {
            return createEncryptCipher().doFinal(data, inputOffset, inputLength);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return data;
    }

    @Override
    public byte[] encrypt(String data) {
        if (data == null) {
            return null;
        }
        return encrypt(data.getBytes(charset));
    }

    /**
     * 加密为base64字符串
     *
     * @param data 源数据
     * @return base64字符串
     */
    public String encryptToBase64(byte[] data) {
        return getBase64Utils().encodeToString(encrypt(data));
    }

    /**
     * 加密为base64字符串
     *
     * @param data 源数据
     * @return base64字符串
     */
    public String encryptToBase64(String data) {
        if (data == null) {
            return null;
        }
        return getBase64Utils().encodeToString(encrypt(data.getBytes(charset)));
    }

    /**
     * 解密
     *
     * @param data 加密后的字符数组
     * @return 源数据
     */
    public byte[] decrypt(byte[] data) {
        if (data == null) {
            return null;
        }
        try {
            return createDecryptCipher().doFinal(data);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return data;
    }

    @Override
    public byte[] decrypt(byte[] data, int inputOffset, int inputLength) {
        if (data == null) {
            return null;
        }
        try {
            return createDecryptCipher().doFinal(data, inputOffset, inputLength);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return data;
    }

    @Override
    public byte[] decrypt(String data) {
        if (data == null) {
            return null;
        }
        return decrypt(getBase64Utils().decode(data));
    }

    /**
     * 解密
     *
     * @param data 加密后的数据
     * @return 源数据
     */
    public String decryptToString(byte[] data) {
        byte[] decrypt = decrypt(data);
        if (decrypt != null) {
            return new String(decrypt, 0, decrypt.length, charset);
        }
        return null;
    }

    /**
     * 解密
     *
     * @param data 加密后的Base64字符串
     * @return 源数据
     */
    public String decryptToString(String data) {
        byte[] decrypt = decrypt(getBase64Utils().decode(data));
        if (decrypt != null) {
            return new String(decrypt, 0, decrypt.length, charset);
        }
        return null;
    }

    /**
     * 加密数据流
     *
     * @param in  待加密的输入数据流
     * @param out 加密后的输出数据流
     */
    @Override
    public void encrypt(InputStream in, OutputStream out) {
        CipherInputStream cipherInputStream = null;
        try {
            //创建加密流
            Cipher encryptCipher = createEncryptCipher();
            cipherInputStream = new CipherInputStream(in, encryptCipher);
            byte[] buf = new byte[1024];//每次读取字节
            int readLength;
            //加密流写入文件
            while ((readLength = cipherInputStream.read(buf)) > 0) {
                out.write(buf, 0, readLength);
            }
            out.flush();
            cipherInputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            IOUtils.close(cipherInputStream);
            IOUtils.close(out);
            IOUtils.close(in);

        }
    }

    /**
     * 解密数据流
     *
     * @param in  待解密的输入数据流
     * @param out 解密后的输出数据流
     */
    @Override
    public void decrypt(InputStream in, OutputStream out) {
        CipherOutputStream cipherOutputStream = null;
        try {
            //创建解密流
            Cipher encryptCipher = createDecryptCipher();
            cipherOutputStream = new CipherOutputStream(out, encryptCipher);
            byte[] buf = new byte[1024];//每次读取字节
            int readLength;
            //加密流写入文件
            while ((readLength = in.read(buf)) > 0) {
                cipherOutputStream.write(buf, 0, readLength);
            }
            cipherOutputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            IOUtils.close(cipherOutputStream);
            IOUtils.close(out);
            IOUtils.close(in);

        }
    }

    /**
     * 计算加密后的秘文长度
     *
     * @param byteSize
     * @param padding  是否填充
     * @return
     */
    public static int calculateSecretLength(int byteSize, boolean padding) {
        if (padding) {
            return 16 * (byteSize / 16 + 1);
        } else {
            return byteSize;
        }
    }


}
